package me.zhengjie.utils;

import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.template.*;
import lombok.extern.slf4j.Slf4j;
import me.zhengjie.domain.GenConfig;
import me.zhengjie.domain.vo.ColumnInfo;
import me.zhengjie.domain.vo.TableInfo;
import org.apache.commons.configuration.Configuration;
import org.springframework.util.ObjectUtils;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.time.LocalDate;
import java.util.*;

/**
 * 代码生成
 *
 * @author Zheng Jie
 * @date 2019-01-02
 */
@Slf4j
public class GenUtil {

    private static final String TIMESTAMP = "Timestamp";

    private static final String BIGDECIMAL = "BigDecimal";

    private static final String PK = "PRI";

    private static final String EXTRA = "auto_increment";

    private static final String ENTITY_MODULE_NAME = "entity_module_name";

    private static final String ENTITY_ROOT_PACKGE = "entity_root_packge";

    public static final class AllTemplateNames {
        public static final String ENTITY = "Entity";
        public static final String DAO = "Dao";
        public static final String SERVICE = "Service";
        public static final String SERVICE_IMPL = "ServiceImpl";
        public static final String CONTROLLER = "Controller";
        public static final String PAGE_REQUEST = "request/PageRequest";
        public static final String CREATE_REQUEST = "request/CreateRequest";
        public static final String UPDATE_REQUEST = "request/UpdateRequest";
        public static final String GET_RESPONSE = "response/GetResponse";
        public static final String PAGE_RESPONSE = "response/PageResponse";
        public static final String DAO_XML = "Dao.xml";
        public static final String SQL = "Sql";
    }


    /**
     * 获取前端代码模板名称
     *
     * @return
     */
    public static List<String> getFrontTemplateNames() {
        List<String> templateNames = new ArrayList<>();
        templateNames.add("api");
        templateNames.add("index");
        templateNames.add("header");
        templateNames.add("edit");
        templateNames.add("eForm");
        return templateNames;
    }

    /**
     * 生成代码
     *
     * @param columnInfos 表元数据
     * @param genConfig   生成代码的参数配置，如包路径，作者
     */
    public static void generatorCode(List<ColumnInfo> columnInfos, GenConfig genConfig, TableInfo tableInfo) throws IOException {
        String tableName = tableInfo.getTableName();
        Map<String, Object> map = getTemplateValue(columnInfos, genConfig, tableInfo);
        TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig("template", TemplateConfig.ResourceMode.CLASSPATH));

        String className = StringUtils.toCapitalizeCamelCase(tableName);
        //生成后台代码
        if (StrUtil.isNotBlank(genConfig.getServerTemples())) {
            List<String> templates = Arrays.asList(genConfig.getServerTemples().split(","));
            for (String templateName : templates) {
                Template template = engine.getTemplate("generator/server/" + templateName + ".ftl");
                String filePath = getServerFilePath(templateName, genConfig, className);

                File file = new File(filePath);

                // 如果非覆盖生成
                if (!genConfig.getCover()) {
                    if (FileUtil.exist(file)) {
                        continue;
                    }
                }
                // 生成代码
                genFile(file, template, map);
            }
        }

        // 生成前端代码
        if (StrUtil.isNotBlank(genConfig.getFrontTemples())) {
            List<String> templates = Arrays.asList(genConfig.getFrontTemples().split(","));
            for (String templateName : templates) {
                Template template = engine.getTemplate("generator/front/" + templateName + ".ftl");
                String filePath = getFrontFilePath(templateName, genConfig, map.get("changeClassName").toString());

                File file = new File(filePath);

                // 如果非覆盖生成
                if (!genConfig.getCover()) {
                    if (FileUtil.exist(file)) {
                        continue;
                    }
                }
                // 生成代码
                genFile(file, template, map);
            }
        }
    }


    /**
     * 模板中用到的变量
     *
     * @param columnInfos
     * @param genConfig
     * @param tableInfo
     * @return
     */
    private static Map<String, Object> getTemplateValue(List<ColumnInfo> columnInfos, GenConfig genConfig, TableInfo tableInfo) {
        String tableName = tableInfo.getTableName();
        String tableComment = tableInfo.getTableComment();

        Configuration configuration = ColUtil.getConfig();
        Map<String, Object> map = new HashMap();
        map.put("package", genConfig.getPack());
        map.put("moduleName", genConfig.getModuleName());
        map.put("author", genConfig.getAuthor());
        map.put("date", LocalDate.now().toString());
        map.put("tableName", tableName);
        map.put("tableComment", StringUtils.removeEnd(tableComment, "表"));
        map.put("genMode", genConfig.getGenMode());
        String className = StringUtils.toCapitalizeCamelCase(tableName);
        String changeClassName = StringUtils.toCamelCase(tableName);

        // 判断是否去除表前缀
        if (StringUtils.isNotEmpty(genConfig.getPrefix())) {
            className = StringUtils.toCapitalizeCamelCase(StrUtil.removePrefix(tableName, genConfig.getPrefix()));
            changeClassName = StringUtils.toCamelCase(StrUtil.removePrefix(tableName, genConfig.getPrefix()));
        }
        map.put("className", className);
        map.put("upperCaseClassName", className.toUpperCase());
        map.put("changeClassName", changeClassName);
        map.put("hasTimestamp", false);
        map.put("hasBigDecimal", false);
        map.put("hasQuery", false);
        map.put("auto", false);
        map.put("entityPackage", configuration.getString(ENTITY_ROOT_PACKGE));
        map.put("secondModuleName", getSecondModuleName(genConfig));

        List<Map<String, Object>> columns = new ArrayList<>();
        List<Map<String, Object>> queryColumns = new ArrayList<>();
        for (ColumnInfo column : columnInfos) {
            Map<String, Object> listMap = new HashMap();
            listMap.put("columnComment", column.getColumnComment());
            listMap.put("columnKey", column.getColumnKey());

            String colType = ColUtil.cloToJava(column.getColumnType().toString());
            String changeColumnName = StringUtils.toCamelCase(column.getColumnName().toString());
            String capitalColumnName = StringUtils.toCapitalizeCamelCase(column.getColumnName().toString());
            String underScoreCaseColumnName = StringUtils.toUnderScoreCase(column.getColumnName().toString());
            if (PK.equals(column.getColumnKey())) {
                map.put("pkColumnType", colType);
                map.put("pkChangeColName", changeColumnName);
                map.put("pkCapitalColName", capitalColumnName);
            }
            if (TIMESTAMP.equals(colType)) {
                map.put("hasTimestamp", true);
            }
            if (BIGDECIMAL.equals(colType)) {
                map.put("hasBigDecimal", true);
            }
            if (EXTRA.equals(column.getExtra())) {
                map.put("auto", true);
            }
            listMap.put("columnType", colType);
            listMap.put("columnName", column.getColumnName());
            listMap.put("isNullable", column.getIsNullable());
            listMap.put("columnShow", column.getColumnShow());
            listMap.put("changeColumnName", changeColumnName);
            listMap.put("capitalColumnName", capitalColumnName);
            listMap.put("underScoreCaseColumnName", underScoreCaseColumnName);

            if (!StringUtils.isBlank(column.getColumnQuery())) {
                listMap.put("columnQuery", column.getColumnQuery());
                map.put("hasQuery", true);
                queryColumns.add(listMap);
            }
            columns.add(listMap);
        }
        map.put("columns", columns);
        map.put("queryColumns", queryColumns);
        return map;
    }


    /**
     * 定义后端文件路径以及名称
     */
    public static String getServerFilePath(String templateName, GenConfig genConfig, String className) {
        String ProjectPath = System.getProperty("user.dir") + File.separator + genConfig.getModuleName();
        String packagePath = ProjectPath + File.separator + "src" + File.separator + "main" + File.separator + "java" + File.separator;
        String resourcesPath = ProjectPath + File.separator + "src" + File.separator + "main" + File.separator + "resources" + File.separator;

        String secondModuleName = getSecondModuleName(genConfig);
        if (!ObjectUtils.isEmpty(genConfig.getPack())) {
            packagePath += genConfig.getPack().replace(".", File.separator) + File.separator;
        }

        if (AllTemplateNames.ENTITY.equals(templateName)) {
            return getEntityFilePath(className, secondModuleName);
        }

        if (AllTemplateNames.DAO.equals(templateName)) {
            return packagePath + "dao" + File.separator + className + "Dao.java";
        }

        if (AllTemplateNames.SERVICE.equals(templateName)) {
            return packagePath + "service" + File.separator + className + "Service.java";
        }

        if (AllTemplateNames.SERVICE_IMPL.equals(templateName)) {
            return packagePath + "service" + File.separator + "impl" + File.separator + className + "ServiceImpl.java";
        }

        if (AllTemplateNames.CONTROLLER.equals(templateName)) {
            return packagePath + "controller" + File.separator + className + "Controller.java";
        }

        if (AllTemplateNames.DAO_XML.equals(templateName)) {
            return resourcesPath + "dao" + File.separator + secondModuleName + File.separator + className + "Dao.xml";
        }

        //request
        if (AllTemplateNames.CREATE_REQUEST.equals(templateName)) {
            return packagePath + "contract" + File.separator + "request" + File.separator + secondModuleName + File.separator + className + "CreateRequest.java";
        }
        if (AllTemplateNames.PAGE_REQUEST.equals(templateName)) {
            return packagePath + "contract" + File.separator + "request" + File.separator + secondModuleName + File.separator + className + "PageRequest.java";
        }
        if (AllTemplateNames.UPDATE_REQUEST.equals(templateName)) {
            return packagePath + "contract" + File.separator + "request" + File.separator + secondModuleName + File.separator + className + "UpdateRequest.java";
        }

        //response
        if (AllTemplateNames.GET_RESPONSE.equals(templateName)) {
            return packagePath + "contract" + File.separator + "response" + File.separator + secondModuleName + File.separator + className + "GetResponse.java";
        }
        if (AllTemplateNames.PAGE_RESPONSE.equals(templateName)) {
            return packagePath + "contract" + File.separator + "response" + File.separator + secondModuleName + File.separator + className + "PageResponse.java";
        }

        //sql

        if (AllTemplateNames.SQL.equals(templateName)) {
            return resourcesPath + "sql" + File.separator + secondModuleName + File.separator + className + "-dml.sql";
        }

        return null;
    }

    /**
     * 定义前端文件路径以及名称
     */
    public static String getFrontFilePath(String templateName, GenConfig genConfig, String apiName) {
        String path = genConfig.getPath();

        if ("api".equals(templateName)) {
            return genConfig.getApiPath() + File.separator + apiName + ".js";
        }

        if ("index".equals(templateName)) {
            return path + File.separator + "index.vue";
        }

        if ("header".equals(templateName)) {
            return path + File.separator + "module" + File.separator + "header.vue";
        }

        if ("edit".equals(templateName)) {
            return path + File.separator + "module" + File.separator + "edit.vue";
        }

        if ("eForm".equals(templateName)) {
            return path + File.separator + "module" + File.separator + "form.vue";
        }
        return null;
    }

    /**
     * 生成
     *
     * @param file
     * @param template
     * @param map
     * @throws IOException
     */
    public static void genFile(File file, Template template, Map<String, Object> map) throws IOException {
        // 生成目标文件
        Writer writer = null;
        try {
            FileUtil.touch(file);
            writer = new FileWriter(file);
            template.render(map, writer);
        } catch (TemplateException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            writer.close();
        }
    }

    /**
     * 获取实体对象保存路径
     *
     * @param secondModuleName 模块名
     * @return
     */
    public static String getEntityFilePath(String className, String secondModuleName) {
        Configuration config = ColUtil.getConfig();
        String domainModuleName = config.getString(ENTITY_MODULE_NAME);
        String domainRootPackge = config.getString(ENTITY_ROOT_PACKGE);

        if (domainModuleName == null || domainModuleName.isEmpty()) {
            throw new IllegalArgumentException("generator.properties 缺少实体模块名配置：entity_module_name=xx-domain");
        }
        if (domainRootPackge == null || domainRootPackge.isEmpty()) {
            throw new IllegalArgumentException("generator.properties 缺少实体基础包名配置：entity_root_packge=com.xx.domain");
        }

        StringBuilder response = new StringBuilder();
        response.append(System.getProperty("user.dir") + File.separator + domainModuleName);
        response.append(File.separator + "src" + File.separator + "main" + File.separator + "java" + File.separator);
        response.append(domainRootPackge.replace(".", File.separator) + File.separator);
        response.append(secondModuleName + File.separator + className + ".java");
        return response.toString();
    }

    /**
     * 获取二级模块
     *
     * @param genConfig
     * @return
     */
    public static String getSecondModuleName(GenConfig genConfig) {
        return genConfig.getPack().substring(genConfig.getPack().lastIndexOf(".") + 1);
    }
}
